﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System;
using System.Globalization;
using System.Runtime.InteropServices;

namespace Tesla.Math {

    /// <summary>
    /// Defines a 3D ray with a point of origin and a direction.
    /// </summary>
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct Ray : IEquatable<Ray> {

        /// <summary>
        /// Origin of this ray.
        /// </summary>
        public Vector3 Origin;

        /// <summary>
        /// Direction that this ray is pointing in.
        /// </summary>
        public Vector3 Direction;

        /// <summary>
        /// Creates a new instance of Ray.
        /// </summary>
        /// <param name="origin">Origin of the ray</param>
        /// <param name="direction">Direction of the ray</param>
        public Ray(Vector3 origin, Vector3 direction) {
            this.Origin = origin;
            this.Direction = direction;
            this.Direction.Normalize();
        }

        /// <summary>
        /// Performs a parametric Ray-Triangle test.
        /// </summary>
        /// <param name="ray">Ray</param>
        /// <param name="tri">Triangle</param>
        /// <returns>Intersection records, if there is one</returns>
        public static IntersectionRecord? Intersects(ref Ray ray, ref Triangle tri) {

            Vector3 p0 = tri.PointA;
            Vector3 p1 = tri.PointB;
            Vector3 p2 = tri.PointC;

            Vector3 u;
            Vector3.Subtract(ref p1, ref p0, out u);
            Vector3 v;
            Vector3.Subtract(ref p2, ref p0, out v);
            Vector3 norm;
            Vector3.Cross(ref u, ref v, out norm);

            //Triangle is degenerate
            if(norm == Vector3.Zero) {
                return null;
            }

            Vector3 w0 = ray.Origin - p0;
            float a;
            Vector3.Dot(ref norm, ref w0, out a);
            a *= -1;
            float b;
            Vector3.Dot(ref norm, ref ray.Direction, out b);

            //Ray is parallel to triangle plane
            if(System.Math.Abs(b) < MathHelper.ZeroTolerance) {
                return null;
            }

            //Get the intersection point
            float r = a / b;
            //Ray does not intersect triangle as its pointing in the opposite direction
            if(r < 0.0) {
                return null;
            }

            //Ray-plane intersection point
            Vector3 iPoint;
            Vector3.Multiply(ref ray.Direction, r, out iPoint);
            Vector3.Add(ref iPoint, ref ray.Origin, out iPoint);

            //Check if intersection is actually inside of the triangle
            float uu;
            Vector3.Dot(ref u, ref u, out uu);
            float uv;
            Vector3.Dot(ref u, ref v, out uv);
            float vv;
            Vector3.Dot(ref v, ref v, out vv);
            Vector3 w;
            Vector3.Subtract(ref iPoint, ref p0, out w);
            float wu;
            Vector3.Dot(ref w, ref u, out wu);
            float wv;
            Vector3.Dot(ref w, ref v, out wv);
            float D = (uv * uv) - (uu * vv);

            //Test parametric coords
            float s, t;
            s = ((uv * wv) - (vv * wu)) / D;
            //Intersection is outside of the triangle
            if(s < 0.0f || s > 1.0f) {
                return null;
            }
            t = ((uv * wu) - (uu * wv)) / D;
            //Again outside
            if(t < 0.0f || (s + t) > 1.0f) {
                return null;
            }

            norm.Normalize();
            return new IntersectionRecord(iPoint, norm, r, tri);
        }

        /// <summary>
        /// Tests equality between this ray and another.
        /// </summary>
        /// <param name="ray">Ray to compare</param>
        /// <returns>True if equal</returns>
        public bool Equals(Ray ray) {
            return ((this.Origin == ray.Origin) && (this.Direction == ray.Direction));
        }

        /// <summary>
        /// Tests equality between two rays.
        /// </summary>
        /// <param name="a">First ray</param>
        /// <param name="b">Second ray</param>
        /// <returns>True if coordinates are equal</returns>
        public static bool operator==(Ray a, Ray b) {
            return ((a.Origin == b.Origin) && (a.Direction == b.Direction));
        }

        /// <summary>
        /// Tests inequality between two rays.
        /// </summary>
        /// <param name="a">First ray</param>
        /// <param name="b">Second ray</param>
        /// <returns>True if coordinates are not equal</returns>
        public static bool operator!=(Ray a, Ray b) {
            return !(a == b);
        }

        /// <summary>
        /// Tests equality between this ray and the supplied object.
        /// </summary>
        /// <param name="obj">Object to compare</param>
        /// <returns>True if object is a ray and contents are equal</returns>
        public override bool Equals(object obj) {
            if(obj is Ray) {
                return Equals((Ray) obj);
            }
            return false;
        }

        /// <summary>
        /// Get the object's hash code.
        /// </summary>
        /// <returns>Hash code</returns>
        public override int GetHashCode() {
            return (this.Direction.GetHashCode() + this.Origin.GetHashCode());
        }

        /// <summary>
        /// Get a string representation of the object.
        /// </summary>
        /// <returns>String representation</returns>
        public override String ToString() {
            return String.Format(CultureInfo.CurrentCulture, "{{Origin: {0} Direction: {1}}}", new Object[] { this.Origin.ToString(), this.Direction.ToString() });
        }
    }
}
